查看原文
其他

利用SEH异常处理机制绕过GS保护

2017-12-25 IronMannnn 看雪学院



实验环境:Win7 sp1 x64

实验工具:vs2013 ImmunityDebug mona.py


本节内容需要了解两个概念:一个是 Windows 异常处理机制(SEH),另一个是 GS 保护。


SEH:

(Structured Exception Hadnling)结构化异常处理 是(Windows)操作系统提供给程序设计者的强有力的处理程序错误或异常的武器


C语言中通常通过_try catch 来实现:


int main()
{
    _try{
           //可能出现异常崩溃的代码
       }
   _except(EXCEPTION_EXECUTE_HANDLER) {
           //异常处理程序
       }
   return 0;
}


其实Windows在原始的程序栈前面添加了一个异常处理结构,该结构由一系列的异常处理链表组成,这条链表的起始点总是放在TIB(Thread Information Block)的第一个成员中,在x86计算机中存储在FS:[0]寄存器中。链表的最后总是默认处理程序,这个默认处理程序的指针总是0xFFFFFFFF

GS保护机制:

        

Windows在VS7.0(Visual Studio 2003)及以后版本的VisualStudio中默认启动了一个安全编译选项——GS(针对缓冲区溢出时覆盖函数返回地址这一特征)


GS保护机制是在函数即将调用的时候向栈桢压入一个DWORD的随机值,同时也向.data段中存放一个Security Cookies,


        1.   被压入栈中的随机值位于EBP之前.在.data段中的数据实现栈Cookies的校验

        2.   在函数返回之前,系统将会执行一个额外的安全验证操作,被称作SecurityCheck

        3.   Security当校验发现栈Cookies和 .data的副本不吻合则表明发生溢出

        4.   当检测到栈中发生溢出时,系统接管异常,函数不会被正常返回,ret指令也不会被执行

        5.    当栈中发生溢出时,Security Cookie将被首先淹没,之后才是EBP和返回地址



GS保护机制的实现细节



1   系统以.data段的第一个DWORD 作为Cookie的种子

2   每次程序运行时的Cookie的种子都不一样,随机性很强

3   栈桢初始化完毕后用EBP异或种子,作为当前函数的Cookie,以此区别不同函数,增强Cookie的随机性

4    在函数返回前,用EBP异或还原出Cookie种子



绕过GS安全保护的方案



1)  通过覆盖SEH链表来阻止系统接管异常处理.

2)  通过改写C++虚表指针来控制程序流程     #msvcrt 采用进程堆后失效

3)  用一些未开启GS安全保护的函数进行溢出(可能是关键字保护) ||小于四字节的Buf

程序很简单,读取文件来还原溢出场景,我将print函数放在了ReadFile下面,可以看到在ShowFileInfo中Printf函数下面有一个Security_Check_Cookie 这就是我们的GS缓冲区检测机制的这个函数,工程项目是realse版本的,在项目属性只开启GS。关闭 优化选项,dep,aslr,safeseh(vs项目属性选择配置属性->链接器->命令行填写“/SAFESEH:NO ”)


我们可以试试如果和上次一样覆盖掉返回地址当执行到Security_Check_Cookie的时候,他会检查栈Cookies和 .data的副本,这时候GS就分发系统异常处理请求然后就由系统接管处理你这个异常 我们可以先用mona插件查看程序当前seh链表

这个地址指向的就是Pointer to next SEHrecord 下面的SE hander是ntdll中的系统接管处理。


我们现在的思路就是覆盖掉这个SEH异常处理链表SEH handler 还需要18*4个字节才能覆盖掉我们用mona生成一个1024+72字节的字符串。


命令行参数重新载入不要忘了Imdebug加参数启动,现在可以看到已经覆盖掉了SEH handler了。

我们继续单步到Printf 看到了参数已经被我们覆盖掉了。


接下来就是寻找个跳板了。


seh通常利用的是pop pop ret 一旦进入异常处理,就会把Pointer to next SEH的这个地址压入栈中进行系统处理,通过pop pop然后这个地址ret到我们的eip中,因为Pointer to Next..是可控的所以我们控制这个地址来控制eip,然后就是可以通过mona来找pop pop ret 来覆盖Se handler。


找到这一行PAGE_EXECUTE_READ,后面的保护机制都是 false。


Address=00401804 Message= 0x00401804 : pop edi # pop esi # ret | startnull,ascii {PAGE_EXECUTE_READ} 找到一个ppr的地址我们打开十六进制编辑器以小端存储将尾部四个字节覆盖掉。


我们已经知道了当执行完我们的pop popret 会把上一个函数的地址弹到eip中我们这个地址也是我们可以控制的。


我们在Pointer to next SEH record地址做一个跳板跳转至我们的shellcode中但是当前地址已经离栈底很近了,所以我们要把shellcode放置在我们的buf中,向上跳。


但是这里还有一个细节就是当你向上跳转的时候是jmp一个负的地址,那么这条jmp XXXX 这条指令就会撑爆当前的这四个字节空间,覆盖掉了后面的se handler数据,所以我们要先在下面找到一个比较近的一块空区域然后在那块区域的地址上写上我们jmp shellcode的指令,我们选择 0018FF8O这个地址。


现在我们加长我们的文本,以看到下图中:现在搜索我们的pop pop ret指令,反汇编窗口ctrl+G 输入00401804在然后再pop上下断点,shitf+f9运行观察eip,当执行完ret指令后,当前指令修改为jmp 0018FF80。

 

上图是覆盖 Pointer to NextRecord 为 jmp 0018FF80 为向下跳转,单步一步,然后这里需要一个向上跳转的jmp这里就用我们文本的第一个字节作为shellcode起始位置0018FF80 jmp 0018FF80的二进制是 E9 B3 FB FF




此时0018FF80地址处的指令就是jmp 0018FF80 单步就到了我们的buf头了



可以看到我们覆盖的数据了,二进制是这样的




接下来我们就在buf中扣一段shellcode在0018FF80这里写指令跳到它的首地址直接用第一个字节的地址 0018FB38




总结



1. 找到SEH处理函数,寻找跳板pop pop ret来覆盖掉ntdll中的 seHandler                                       

2. 构造跳板跳向shellcode,字节长度问题可以在seHandler下方找跳板间接找跳板跳向 shellcode   




本文由看雪论坛 IronMannnn 原创

转载请注明来自看雪社区


热门阅读




点击阅读原文/read,

更多干货等着你~




您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存